/*******************************************************************************
 *     ___                  _   ____  ____
 *    / _ \ _   _  ___  ___| |_|  _ \| __ )
 *   | | | | | | |/ _ \/ __| __| | | |  _ \
 *   | |_| | |_| |  __/\__ \ |_| |_| | |_) |
 *    \__\_\\__,_|\___||___/\__|____/|____/
 *
 *  Copyright (c) 2014-2019 Appsicle
 *  Copyright (c) 2019-2024 QuestDB
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

package io.questdb.griffin.engine.functions.table;

import io.questdb.cairo.CairoConfiguration;
import io.questdb.cairo.sql.Function;
import io.questdb.cairo.sql.RecordCursorFactory;
import io.questdb.griffin.FunctionFactory;
import io.questdb.griffin.SqlException;
import io.questdb.griffin.SqlExecutionContext;
import io.questdb.griffin.engine.functions.CursorFunction;
import io.questdb.griffin.engine.functions.catalogue.FilesFunctionFactory;
import io.questdb.griffin.engine.functions.columns.VarcharColumn;
import io.questdb.griffin.engine.functions.constants.StrConstant;
import io.questdb.griffin.engine.functions.regex.GlobStrFunctionFactory;
import io.questdb.griffin.engine.table.FilteredRecordCursorFactory;
import io.questdb.std.Chars;
import io.questdb.std.IntList;
import io.questdb.std.ObjList;


/**
 * Provides a pseudo table returning file data based on a glob pattern.
 */
public class GlobFilesFunctionFactory implements FunctionFactory {
    private final FilesFunctionFactory filesFunctionFactory = new FilesFunctionFactory();
    private final GlobStrFunctionFactory globStrFactory = new GlobStrFunctionFactory();

    /**
     * Extracts the non-glob prefix from a glob pattern.
     * <p>
     * Examples:
     * <ul>
     *   <li>"pattern/file_*.parquet" -&gt; "pattern/"</li>
     *   <li>"/path/to/data/{@literal *}/file_?.csv" -&gt; "/path/to/data/"</li>
     *   <li>"/abs/path/to/file*.txt" -&gt; "/abs/path/to/"</li>
     * </ul>
     * <p>
     * The function stops at the first glob metacharacter: {@literal *}, ?, [, or ]
     * It excludes the directory separator before the first glob pattern.
     *
     * @param globPattern the glob pattern string
     * @return the non-glob prefix (everything up to and including the last separator before the first glob char)
     */
    public static CharSequence extractNonGlobPrefix(CharSequence globPattern) {
        if (Chars.isBlank(globPattern)) {
            return "";
        }

        int lastSlashIndex = -1;
        for (int i = 0; i < globPattern.length(); i++) {
            char c = globPattern.charAt(i);

            // Check if this is a glob metacharacter
            if (c == '*' || c == '?' || c == '[' || c == ']') {
                // We found a glob pattern, return excluding the last slash
                if (lastSlashIndex >= 0) {
                    return globPattern.subSequence(0, lastSlashIndex);
                } else {
                    // No slash before the glob, return empty
                    return "";
                }
            }

            // Track the last slash we've seen
            if (c == '/' || c == '\\') {
                lastSlashIndex = i;
            }
        }

        // No glob patterns found - return the entire pattern
        return globPattern;
    }

    @Override
    public String getSignature() {
        return "glob(s)";
    }

    @Override
    public Function newInstance(int position, ObjList<Function> args, IntList argPositions, CairoConfiguration configuration, SqlExecutionContext sqlExecutionContext) throws SqlException {
        final Function arg = args.getQuick(0);
        assert arg.isConstant();

        CharSequence glob = arg.getStrA(null);
        CharSequence root = extractNonGlobPrefix(glob);
        if (!Chars.equals(glob, root)) {
            glob = glob.subSequence(root.length(), glob.length());
        }

        if (Chars.isBlank(glob)) {
            // then it is just a file scan
            return filesFunctionFactory.newInstance(position, args, argPositions, configuration, sqlExecutionContext);
        }

        // strip leading char
        if (glob.charAt(0) == '/' || glob.charAt(0) == '\\') {
            glob = glob.subSequence(1, glob.length());
        }

        ObjList<Function> newArgs = new ObjList<>();
        newArgs.add(new StrConstant(root));

        RecordCursorFactory filesCursor = filesFunctionFactory.newInstance(position, newArgs, argPositions, configuration, sqlExecutionContext).getRecordCursorFactory();

        newArgs.set(0, new VarcharColumn(0));
        newArgs.add(new StrConstant(glob));
        argPositions.add(0);

        assert newArgs.size() == argPositions.size();
        Function globFunc = globStrFactory.newInstance(position, newArgs, argPositions, configuration, sqlExecutionContext);

        return new CursorFunction(new FilteredRecordCursorFactory(filesCursor, globFunc));
    }
}